package se.thoka.twt.client.item;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import se.thoka.twt.client.event.DatastoreChangedEvent;
import se.thoka.twt.client.event.DatastoreChangedHandler;
import se.thoka.twt.client.event.HasDatastoreChangedHandlers;
import se.thoka.twt.client.utils.ErrorReporter;
import se.thoka.twt.client.utils.SortDirection;
import se.thoka.twt.client.utils.TWTLogger;
import se.thoka.twt.shared.item.ItemServiceAsync;

import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;


public abstract class ItemView<T extends Item<T>> implements IsWidget, AsyncCallback<Collection<T>>, DatastoreChangedHandler<T>, AttachEvent.Handler {

	private final Map<String, ItemRenderer<T>> rendererMap;
	private final List<ItemRenderer<T>> itemList;
	private final RendererSorter sort;
	private ItemServiceAsync<T> service;
	private HandlerRegistration datastoreChangedRegistration;
	private boolean loadQueued;
	
	public ItemView() {
		rendererMap = new HashMap<String, ItemRenderer<T>>();
		itemList = new ArrayList<ItemRenderer<T>>();
		sort = new RendererSorter();
		
		loadQueued = true;
	}
	
	public ItemView(ItemServiceAsync<T> service) {
		this();
		
		setDatastore(service);
	}
	
	@Override
	public Widget asWidget() {
		ItemViewRenderer<T> renderer = getViewRenderer();
		if (renderer == null) {
			throw new IllegalStateException("View renderer must not be null.");
		}
		Widget result = renderer.asWidget();
		result.addAttachHandler(this);
		
		return result;
	}
	
	@Override
	public void onAttachOrDetach(AttachEvent event) {
		if (event.isAttached() && loadQueued) {
			reload();
		}
	}
	
	@Override
	public final void onDatastoreChanged(DatastoreChangedEvent<T> event) {
		reload();
	}
	
	@Override
	public void onFailure(Throwable caught) {
		ErrorReporter.onRPCError(caught);
	}
	
	@Override
	public final void onSuccess(Collection<T> result) {
		Set<String> ids = new HashSet<String>();
		
		itemList.clear();
		for (T item : result) {
			String id = item.getId();
			ids.add(id);
			
			boolean render;
			ItemRenderer<T> itemRenderer = rendererMap.get(id);
			if (itemRenderer == null) {
				// New item
				itemRenderer = createItemRenderer();
				render = true;
			} else {
				T oldItem = itemRenderer.getItem();
				render = oldItem.getModified().equals(item.getModified());
			}
			
			itemRenderer.setItem(item);
			if (render) {
				itemRenderer.render();
			}
			itemList.add(itemRenderer);
		}
		
		rendererMap.keySet().retainAll(ids);
		
		refresh();
	}
	
	public void reload() {
		reloadItems(this);
	}
	
	public final void refresh() {
		Collections.sort(itemList, sort);
		
		ItemViewRenderer<T> viewRenderer = getViewRenderer();
		viewRenderer.clear();
		viewRenderer.addAll(itemList);
	}
	
	public boolean setSort(Comparator<T> comparator) {
		return this.sort.setItemComparator(comparator);
	}
	
	public boolean setSortDirection(SortDirection sortDirection) {
		return this.sort.setSortDirection(sortDirection);
	}
	
	public void setDatastore(ItemServiceAsync<T> service) {
		if (datastoreChangedRegistration != null) {
			datastoreChangedRegistration.removeHandler();
		}
		if (service instanceof HasDatastoreChangedHandlers) {
			@SuppressWarnings("unchecked")
			HasDatastoreChangedHandlers<T> ds = (HasDatastoreChangedHandlers<T>) service;
			datastoreChangedRegistration = ds.addDatastoreChangedHandler(this);
		}
		TWTLogger.log(getClass().getName() + "#setDatastore(" + service.getClass().getName() + ") Added change handler? " + (datastoreChangedRegistration != null));
		this.service = service;
	}
	
	protected abstract ItemViewRenderer<T> getViewRenderer();
	
	protected abstract ItemRenderer<T> createItemRenderer();
	
	protected void reloadItems(AsyncCallback<Collection<T>> callback) {
		if (service == null) {
			throw new IllegalStateException(getClass().getName() + "#reloadItems(...) Service has not been set.");
		}
		
		if (asWidget().isAttached()) {
			loadQueued = false;
			service.selectAll(callback);
		} else {
			loadQueued = true;
		}
	}
	
	private class RendererSorter implements Comparator<ItemRenderer<T>> {
		private Comparator<T> itemComparator;
		private SortDirection sortDirection;
		@Override
		public int compare(ItemRenderer<T> o1, ItemRenderer<T> o2) {
			int result;
			if (itemComparator != null) {
				result = itemComparator.compare(o1.getItem(), o2.getItem());
			} else {
				result = o1.getItem().compareTo(o2.getItem());
			}
			if (sortDirection == SortDirection.DESC) {
				result *= -1;
			}
			
			return result;
		}
		public boolean setItemComparator(Comparator<T> comparator) {
			boolean comparatorChanged = this.itemComparator != comparator;
			this.itemComparator = comparator;
			
			return comparatorChanged;
		}
		public boolean setSortDirection(SortDirection sortDirection) {
			boolean directionChanged = this.sortDirection != sortDirection;
			this.sortDirection = sortDirection;
			
			return directionChanged;
		}
	}
}
